import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.*;

/**
 * @author salim
 */

public class Client extends UnicastRemoteObject implements Client_itf {

	private static final long serialVersionUID = 1L;
	private static Client privateClient; 
	private static Server_itf remoteServer;
	private static HashMap<Integer, SharedObject> sharedObjectsMap;
	
	public Client() throws RemoteException {
		super();
		System.out.println("Client : Client");
	}


///////////////////////////////////////////////////
//         Interface to be used by applications
///////////////////////////////////////////////////

	/** initialization of the client layer
	 * 
	 * + initialize the remote server whose methodes will be
	 * invokated 
	 * + initialize the sharedObjects list
	 * + instantiate a private client
	 * 
	 * 
	 * @throws NotBoundException 
	 * @throws MalformedURLException 
	 * @throws RemoteException  
	 * 
	 */
	public static void init() throws RemoteException, MalformedURLException, NotBoundException {
		System.out.println("Client : init");
		
		// get a reference to the remote server
		remoteServer = (Server_itf) Naming.lookup("//localhost:8081/remoteServer");
		// initialize the sharedObjects list
		sharedObjectsMap = new HashMap<Integer, SharedObject>();
		
		// instanciate an invisibe client
		privateClient = new Client();
	}
	
	/* lookup in the name server
	 * Version 1: Get The shared Objet from the server
	 * without checking if the shared object already exists locally
	 */
	public static SharedObject lookup(String name) throws RemoteException {
		System.out.println("Client : lookup " + name);
		
		int id = remoteServer.lookup(name);
		
		if (id == -1) {
			return null;
		}
		
		if (sharedObjectsMap.containsKey(id)) {
			return sharedObjectsMap.get(id);
		}else {
			SharedObject so = remoteServer.get(id); 
			sharedObjectsMap.put(id, so);
			return so;
		}
	}		
	
	/** binding in the name server
	 * 
	 * This method registers the shared object so in the server's
	 * namin registry under the name <name>
	 * 
	 * IMPORTANT: The id of the shared object is updated.
	 * And is used to insert the shared object in the rignt place in
	 * the sharedObjectMap
	 * 
	 * @param so
	 * @param name
	 */
	public static void register(String name, SharedObject_itf so) throws RemoteException {
		System.out.println("Client : register " + name);
				
			// create the shared object obj on the server
			// This will override objects of the same name
			int id = remoteServer.create(((SharedObject) so).obj);
			
			// register the object on the naming registry
			remoteServer.register(name, id);
			
			// update the id of the shared object on the the Client
			((SharedObject)so).id = id;
			
			// put the sharedObject in the the sharedObjectMap
			sharedObjectsMap.put(id, (SharedObject)so);
	}

	/** creation of a shared object
	 * returns an instance of SharedObject which
	 * contains the shared object and an non exploitable 
	 * id (-1) because the shared object is not yet 
	 * registered on the server
	 */
	
	public static SharedObject create(Object o) {
		System.out.println("Client : create");
		
		String className = o.getClass().getName();
		Class stubClass;
		
		try {
			stubClass = Class.forName(className + "_stub");
		} catch (ClassNotFoundException e) {
			System.err.println ("Class "+className+" not found.");
			return null;
		}
		
		Class[] parameterTypes = new Class[] {Object.class, int.class};
		
		Constructor constructor = null; 
		
		try {
			constructor = stubClass.getConstructor(parameterTypes);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		Object[] initArgs = new Object[] {o, -1};
		
		SharedObject so = null;
		
		try {
			so = (SharedObject)(constructor.newInstance(initArgs));
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		
		return so;
		
	}
	
/////////////////////////////////////////////////////////////
//    Interface to be used by the consistency protocol
////////////////////////////////////////////////////////////

	/** request a read lock from the server
	 * 
	 * @param id
	 * @return Object
	 */
	public static Object lock_read(int id) {
		System.out.println("Client : lock_read " + id);
		
		while (true)
		{
			try {
				return remoteServer.lock_read(id, privateClient);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	}

	/** request a write lock from the server
	 * 
	 * @param id
	 * @return Object
	 */
	public static Object lock_write(int id) {
		System.out.println("Client : lock_write " + id);
		
		while (true) {
			try {
				return remoteServer.lock_write(id, privateClient);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	}

	/** receive a lock reduction request from the server
	 * 
	 * Indirection
	 */
	public Object reduce_lock(int id) throws RemoteException {
		System.out.println("Client : reduce_lock " + id);
		
		return sharedObjectsMap.get(id).reduce_lock();
	}


	/** receive a reader invalidation request from the server
	 * 
	 * Indirection
	 */
	public void invalidate_reader(int id) throws RemoteException {
		System.out.println("Client : invalidate_reader " + id);
		
		sharedObjectsMap.get(id).invalidate_reader();
	}


	/** receive a writer invalidation request from the server
	 * 
	 * Indirection
	 */
	public Object invalidate_writer(int id) throws RemoteException {
		System.out.println("Client : invalidate_writer "+ id);
		
		return sharedObjectsMap.get(id).invalidate_writer();
	}
}
